(in-package :leam)
(use-package :sb-bsd-sockets)
(declaim (optimize (speed 0)(safety 3)(debug 3)))

(defun nslookup (hostname)
  "Performs a DNS look up for HOSTNAME and returns the address as a
four element array, suitable for socket-connect.  If HOSTNAME is not
found, a host-not-found-error condition is thrown."
  (if hostname
      (host-ent-address (get-host-by-name hostname))
      nil))

(defun make-disconnector (socket id)
  (lambda ()
    (let ((fd (socket-file-descriptor socket)))
      (format t "~a: closing~%" id)
      (sb-impl::invalidate-descriptor fd)
      (socket-close socket))))

(defun accept-connection( socket handler-method id )
  "Accept a connection on a given socket"
  (let* ((client-socket (socket-accept socket))
	 (stream (socket-make-stream client-socket :output t :input t
				     :element-type '(unsigned-byte 8))))
    (funcall handler-method (make-disconnector client-socket id)
	     client-socket stream id)))

(defun create-tcp-server(port handler-method &key (backlog 5))
  "Create a server on the local host with a given port Each new
connection gets a disconnect method, a stream and an id.  A method to
close the server is returned.  The stream is a binary stream"
  (let ((socket (make-instance 'inet-socket :type :stream :protocol :tcp))
        (counter 0))
    (socket-bind socket #(127 0 0 1) port)
    (socket-listen socket backlog)
    (sb-impl::add-fd-handler (socket-file-descriptor socket)
                             :input
                             (lambda (_)
                               (declare (ignore _))
                               (incf counter)
			       (accept-connection
				socket handler-method counter)))
    
    (make-disconnector socket -1)))

(export 'create-tcp-server)


(defun create-stream-server(port socket-handler-creator &key (backlog 5))
  "One step of abstraction above the tcp server, this method simple requests
the client provide a read method and an eof method, returned in a values
form.  The creator takes the disconnector method, the stream, and the
connection id.  The method registers the fd for events, but at some point
the caller will need to call either serve-event or serve-all-events"
  (create-tcp-server
   port
   (lambda (disconnector socket stream id)
     (multiple-value-bind
	   (read-handler eof-handler)
	 (funcall socket-handler-creator disconnector stream id)
       (let ((fd-handler-handle nil))
	 (setf fd-handler-handle
	       (sb-impl::add-fd-handler (socket-file-descriptor socket)
					:input
					(lambda (_)
					  (declare (ignore _))
					  (handler-case
					      (funcall read-handler)
					    (end-of-file ()
					      (funcall eof-handler
						       fd-handler-handle)))))))))
       :backlog backlog))
(export 'create-stream-server)

